#-- vim:sw=2:et
#++
#
# :title: BRAX (Bored of Reason API over XML-RPC)
#
# Author:: Jason Hines <jason@greenhell.com>
# Copyright:: (C) 2008 BoR/PLiK Heavy Development
# License:: N/A
#
# Docs: http://bordom.net/brax/
# Code: http://code.google.com/p/brax/

require "xmlrpc/client"
require "net/http"

define_structure :AuthSession, :address, :token

class BraxPlugin < Plugin

  USERNAME = "bor"
  PASSWORD = "borrpc"
  CHANNEL = "#bor"

  B = 2.chr
  VERSION = "0.81"

  Config.register Config::BooleanValue.new('brax.enabled', :default => true, :desc => "Enable and disable the plugin")
  Config.register Config::ArrayValue.new('brax.ignore_users', :default => [], :desc => "Hostmasks of users to be ignored")          

  def initialize
    super
    @registry.set_default([])
    @registry[:sessions] = Array.new unless @registry.has_key? :sessions
  end

  def rpc_server
    server = XMLRPC::Client.new3({:host=>'www.bordom.net',:path=>'/rpc/',:user=>USERNAME,:password=>PASSWORD})
    server.timeout = 10
    server
  end

  def cleanup
    # Nothing to do
  end

  def topic(m)
    return unless m.topic
    begin
      rpc_server.call("brax.channels.setSlogan", m.topic.to_s)
    rescue XMLRPC::FaultException => e
      log "Error in topic sync: #{e.faultString}" and return
    end                                  
    log "Topic sync: #{m.topic}."
  end

  def join(m)
    return if m.address?
    @registry[:sessions].each { |session|
      @bot.mode(m.channel, "+v", m.sourcenick) if m.sourceaddress == session.address
    }
  end

  def help(plugin, topic="")
    case topic
    when "title", "tit"
      "X: title <title> => Set title to <title> for item 'X'"
    when "url", "link"
      "X: url <address> => Change URL to <URL> for item 'X'"
    when "caption", "cap"
      "X: caption <caption> => Set item caption to <caption>"
    when "excerpt", "exc"
      "X: excerpt <excerpt> => Set item excerpt to <excerpt>"
    when "image", "img"
      "X: image <URL> => Set item image to <URL>"
    when "nick", "author"
      "X: nick <nick> => Change item author to <nick>"
    when "tag"
      "X: tag <word> [, <word>, <word>] => Add tag <word> to item.  Separate multiple tags with commas"
    when "untag"
      "X: untag <word> => Remove tag <word> from item"
    when "hide"
      "X: hide => Hide this item for no apparent reason"
    when "show"
      "X: show => Mark item as visible (unhidden)"
    when "nsfw"
      "X: nsfw => Mark item as NOT safe for work"
    when "sfw"
      "X: sfw => Mark item as SAFE for work"
    when "bump"
      "X: bump => Bump an item"
    when "<3", "love", "heart"
      "X: <3 => Heart an item"
    when "dupe"
      "X: dupe <item_id> => Flag item as a duplicate of <item_id>"
    when "spam"
      "X: spam => Flag item as spam"
    when "online", "who", "sessions"
      "!online => Show who is currently online on bordom.net"
    when "info", "nfo"
      "X: info => Get info about an item"
    when "delcomment", "dc"
      "!delcomment <comment_id> => Deletes comment <comment_id>"
    when "queue", "q"
      "!queue => Show a summary of items currently in the queue"
    when "search", "s"
      "!search [phrase] => Search archive for best match"
    when "quote"
      "!quote [nick] => Show a random quote (by nick, if specified)"
    when "addquote"
      "!addquote [<nick> quote] => Adds a quote by <nick>"
    when "ident", "id", "auth", "identify"
      "Usage: /msg bordom identify <username> <password> => Identify yourself as a user on bordom.net"
    when "undo"
      "X: undo <title|excerpt|caption|image|url> => Undo last edit restoring value to given field's previous value"
    else
      "BRAX Commands: addquote delcomment disable enable identify ignore online search status queue quote\nSee http://bordom.net/help/irc for more information."
    end
  end

  def do_help(m)
    m.reply "BRAX Item commands: bump, caption, dupe, excerpt, hide, heart, image, info, nick, nsfw, swf, show, spam, tag, title, tag, undo, url \nFor individual command help, see !help brax [cmd]"
  end

  def queue(m,params)
    begin
      items = rpc_server.call("brax.items.getItems", {:queued=>1,:nsfw=>1})
    rescue XMLRPC::FaultException => e
      m.reply e.faultString and return
    end
    count = items['page_info']['num_items']
    m.reply "There are #{count} items pending moderation. See http://www.bordom.net/queue"    
    if (count.to_i < 25) 
      for item in items['items']
        @bot.notice m.sourcenick, "#{item['letter']}: #{item['title']} (#{item['approval_percentage']}% approved)"
      end
    end
    log "Queue summary requested"
  end

  def search(m,params)
    begin
      items = rpc_server.call("brax.items.getItems", {:query=>params[:terms].to_s,:nsfw=>1,:public=>1})
    rescue XMLRPC::FaultException => e
      m.reply e.faultString and return
    end
    count = items['page_info']['num_items']
    m.reply "No matches found." and return if count.to_i==0
    if (count.to_i < 11)
      for item in items['items']
        m.reply "#{item['letter']}: #{item['title']} (by #{item['author']}) -> " + shorten_url(item['permalink']+'?source=irc')
      end
    else
      m.reply "More than 10 matches found.  See " + shorten_url("http://www.bordom.net/search/?query=" + CGI.escape(params[:terms].to_s))
    end
    log "Search requested"
  end

  def quote(m,params)
    begin
      if params[:nick]
        quote = rpc_server.call("brax.quotes.getQuoteByNick", params[:nick].to_s)
      else
        quote = rpc_server.call("brax.quotes.getQuote")
      end
    rescue XMLRPC::FaultException => e
      m.reply e.faultString and return
    end
    m.reply "#{quote['quote']} --#{quote['quoted']} (Quote ##{quote['quote_id']} added by #{quote['author']} on #{format_ts(quote['time'])})"
    log "Quote requested"
  end

  def addquote(m,params)
    token = identified_token(m.sourceaddress)

    begin
      quote = rpc_server.call("brax.quotes.addQuote", params[:quote].to_s, token)
    rescue XMLRPC::FaultException => e
      m.reply e.faultString and return
    end
    #m.reply "#{quote['quote']} --#{quote['quoted']} (Quote #{quote['quote_id']} added)"
    log "Quote added"
  end

  def delcomment(m,params)
    begin
      comment = rpc_server.call("brax.comments.getComment", params[:comment_id].to_i)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    if m.sourcenick!=comment['author'] && params[:force]!="--force"
      # only comment owner can delete their own comments
      @bot.notice m.sourcenick, "Cannot delete comment #{params[:comment_id]}. It was added by #{B}#{comment['author']}#{B}, not you." and return
    end

    token = identified_token(m.sourceaddress)

    begin
      rpc_server.call("brax.comments.deleteComment", params[:comment_id].to_i, token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    @bot.notice m.sourcenick, "Comment #{params[:comment_id]} successfully removed from item [#{B}#{comment['item_id']}#{B}]."
    log "Deleted comment #{params[:comment_id]}"
  end

  def online(m,params)
    begin
      users = rpc_server.call("brax.auth.sessions")
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, "Noone is here." and return
    end
    m.reply "Currently online: " + users.map {|u| u['nick']}.join(", ")
  end

  def status(m,params)
    ok,api_version = rpc_server.call2("brax.getVersion")
    if ok
      m.reply "BRAX (RBot Plugin: #{VERSION}) / BRAX API: #{api_version})"
    else
      m.reply "BRAX (RBot Plugin: #{VERSION}) / **Error communicating with BRAX API**"
    end
    if @bot.config['brax.enabled']
      m.reply "BRAX is currently enabled - Monitoring channel #{CHANNEL}"
      #m.reply "Current sessions (#{@registry[:sessions].size}): " + @registry[:sessions].collect { |session| session.address }.join(', ')
    else
      m.reply "BRAX is currently disabled"
    end
  end

  def ignore?(user=nil)
    return false unless user
    @bot.config['brax.ignore_users'].each do |mask|
      return true if user.matches?(mask)
    end
    return false
  end

  def ignore(m, params)
    action = params[:action]
    user = params[:option]
    case action
    when 'remove':
      if @bot.config['brax.ignore_users'].include? user
        s = @bot.config['brax.ignore_users']
        s.delete user
        @bot.config['ignore_users'] = s
        m.reply "#{user} removed from ignore list"
      else
        m.reply "is not being ignored"
      end
    when 'add':
      if user
        if @bot.config['brax.ignore_users'].include?(user)
          m.reply "#{user} is already being ignored"
        else
          @bot.config['brax.ignore_users'] = @bot.config['brax.ignore_users'].push user
          m.reply "#{user} added to ignore list"
        end
      else
        m.reply "Usage: brax ignore add <nick>"
      end
    when 'list':
      m.reply "Ignoring #{@bot.config['brax.ignore_users'].join(", ")}"
    else
      m.reply "Usage: brax ignore <add|remove|list> [nick]"
    end
  end

  def enable(m, params)
    @bot.config['brax.enabled'] = true
    m.okay
  end

  def disable(m, params)
    @bot.config['brax.enabled'] = false
    m.okay
  end

  def listen(m)
    return unless @bot.config['brax.enabled']
    return unless m.kind_of?(PrivMessage) && m.public?
    return if m.address?
    return if m.channel.downcase!=CHANNEL.downcase
    return if ignore? m.source
    return if m.replied?
    message = m.message
    token = identified_token(m.sourceaddress)

    if matches = message.scan(/^([a-zA-Z]|[0-9]+)[:]\s(.*)$/).first
      m.reply "#{m.sourcenick}, please identify yourself first. Use:  /msg bordom identify <username> <password>" and return if token.nil?

      # message is a command
      item_id = matches[0]
      if item_id =~ /^([a-zA-Z])$/
        # id was given as a letter, get the real id
        item_id = rpc_server.call("brax.items.getIdByLetter", item_id.downcase)
      end
      params = matches[1].strip.split(' ')
      command = params[0].downcase
      params.shift
      args = params.join(' ')
      log "Received command '#{command}' args '#{args}' for item '#{item_id}' by #{m.sourcenick}"
      case command
      when "title", "tit"
        m.reply "Usage: " + help(m.plugin,command) and return if args.empty?
        do_title(item_id,m,args,token)
      when "nick", "author"
        m.reply "Usage: " + help(m.plugin,command) and return if args.empty?
        do_author(item_id,m,args,token)
      when "caption", "cap"
        do_caption(item_id,m,args,token)
      when "excerpt", "exc"
        do_excerpt(item_id,m,args,token)
      when "image", "img"
        do_image(item_id,m,args,token)
      when "tag"
        m.reply "Usage: " + help(m.plugin,command) and return if args.empty?
        do_tag(item_id,m,args,token)
      when "untag", "deltag", "deletetag"
        m.reply "Usage: " + help(m.plugin,command) and return if args.empty?
        do_untag(item_id,m,args,token)
      when "url", "link"
        m.reply "Usage: " + help(m.plugin,command) and return if args.empty?
        do_url(item_id,m,args,token)
      when "dupe"
        m.reply "Usage: " + help(m.plugin,command) and return if args.empty?
        do_dupe(item_id,m,args,token)
      when "undo"
        m.reply "Usage: " + help(m.plugin,command) and return if args.empty?
        do_undo(item_id,m,args,token)
      when "spam"
        do_spam(item_id,m,token)
      when "hide"
        do_hide(item_id,m,token)
      when "show", "unhide"
        do_show(item_id,m,token)
      when "nsfw"
        do_nsfw(item_id,m,token)
      when "sfw"
        do_sfw(item_id,m,token)
      when "bump", "freshen"
        do_bump(item_id,m,token)
      when "<3", "love", "heart"
        do_heart(item_id,m,token)
      when "info", "nfo"
        do_info(item_id,m,token)
      when "help"
        do_help(m)
      else
        # assume it's a comment
        do_comment(item_id,m,matches[1],token)
      end
    elsif !token.nil? # Only submit URLs from known ppl
      # message is not an command, does it contain URLs?
      if matches = message.scan(/(.?)(http[s]?:\/\/\S+)/i)
        for url in matches
          log "Matched URL #{url[1]}"
          if url[0]=='~' or url[0]=='+'
            if url[1].length > 80
              surl = shorten_url(url[1])
              m.reply surl unless surl == url[1]
            end
          else
            submit_item(url[1],m,token)
          end
        end
      end
    end
  end

  def do_title(item_id,m,title,token)
    begin
      item = rpc_server.call("brax.items.getItem",item_id.to_i)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    @bot.notice m.sourcenick, "Only items in the queue may be edited from here" and return if item['queued'].to_i==0

    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:title=>title},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Setting title '#{title}' to item #{item_id}"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] successfully titled: #{title}"
  end

  def do_author(item_id,m,author,token)
    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:author=>author},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Setting author '#{author}' to item #{item_id}"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] successfully changed to nick '#{author}'"
  end

  def do_url(item_id,m,url,token)
    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:url=>url},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Setting URL '#{url}' to item #{item_id}"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] URL successfully changed"
  end

  def do_dupe(item_id,m,orig_item_id,token)
    @bot.notice m.sourcenick, "Original item ID #{orig_item_id} is not valid" and return unless orig_item_id =~ /^([0-9]+)$/
    begin
      result = rpc_server.call("brax.items.flagItem",item_id.to_i,"dupe:#{orig_item_id}",token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Flagging '#{item_id}' as a dupe of item #{orig_item_id}"
    if result['demoted']
      @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] has been flagged and buried."
    else
      @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] flagged as a duplicate of item [#{B}#{orig_item_id}#{B}]"
    end
  end

  def do_spam(item_id,m,token)
    begin
      result = rpc_server.call("brax.items.flagItem",item_id.to_i,"spam",token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Flagging '#{item_id}' as spam"
    if result['demoted']
      @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] has been flagged and buried."
    else
      @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] flagged as spam"
    end
  end

  def do_caption(item_id,m,caption,token)
    begin
      item = rpc_server.call("brax.items.getItem",item_id.to_i)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    @bot.notice m.sourcenick, "Only items in the queue may be edited from here" and return if item['queued'].to_i==0

    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:caption=>caption},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Setting caption '#{caption}' to item #{item_id}"
    if caption.empty?
      @bot.notice m.sourcenick, "Caption removed from item [#{B}#{item_id}#{B}]"
    else
      @bot.notice m.sourcenick, "Caption added to item [#{B}#{item_id}#{B}] successfully."
    end
  end

  def do_excerpt(item_id,m,excerpt,token)
    begin
      item = rpc_server.call("brax.items.getItem",item_id.to_i)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    @bot.notice m.sourcenick, "Only items in the queue may be edited from here" and return if item['queued'].to_i==0

    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:excerpt=>excerpt},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Setting excerpt '#{excerpt}' to item #{item_id}"
    if excerpt.empty?
      @bot.notice m.sourcenick, "Excerpt removed from item [#{B}#{item_id}#{B}]"
    else
      @bot.notice m.sourcenick, "Excerpt added to item [#{B}#{item_id}#{B}] successfully."
    end
  end

  def do_image(item_id,m,image,token)
    begin
      item = rpc_server.call("brax.items.getItem",item_id.to_i)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    @bot.notice m.sourcenick, "Only items in the queue may be edited from here" and return if item['queued'].to_i==0

    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:image=>image},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Setting image '#{image}' to item #{item_id}"
    if image.empty?
      @bot.notice m.sourcenick, "Image removed from item [#{B}#{item_id}#{B}]"
    else
      @bot.notice m.sourcenick, "Image added to item [#{B}#{item_id}#{B}] successfully."
    end
  end

  def do_tag(item_id,m,tags,token)
    begin
      item = rpc_server.call("brax.items.addTag",item_id.to_i,tags,token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Tagging item #{item_id} with tags: #{tags}"
    @bot.notice m.sourcenick, "Tag(s) '#{tags}' added to item [#{B}#{item_id}#{B}]"
  end

  def do_untag(item_id,m,tag,token)
    begin
      item = rpc_server.call("brax.items.deleteTag",item_id.to_i,tag)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Deleting tag '#{tag}' from item #{item_id}"
    @bot.notice m.sourcenick, "Tag '#{tag}' removed from item [#{B}#{item_id}#{B}]"
  end

  def do_hide(item_id,m,token)
    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:hidden=>1},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Hiding item #{item_id}"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] is now hidden"
  end

  def do_show(item_id,m,token)
    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:hidden=>0},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Hiding item #{item_id}"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] is no longer hidden"
  end

  def do_nsfw(item_id,m,token)
    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:nsfw=>1},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Setting item #{item_id} NSFW"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] is now set NSFW"
  end

  def do_sfw(item_id,m,token)
    begin
      item = rpc_server.call("brax.items.updateItem",item_id.to_i,{:nsfw=>0},token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Setting item #{item_id} SFW"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] is no longer NSFW"
  end

  def do_bump(item_id,m,token)
    begin
      item = rpc_server.call("brax.items.bumpItem",item_id.to_i,token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Bumping item #{item_id} to front page"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] successfully bumped to the top"
  end

  def do_undo(item_id,m,field,token)
    begin
      edit = rpc_server.call("brax.items.undoEdit",item_id.to_i,token,field)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Undo edit on field #{field} for item #{item_id}"
    @bot.notice m.sourcenick, "Value for '#{field}' restored to '#{edit['new_value']}' for item [#{B}#{item_id}#{B}]"
  end

  def do_heart(item_id,m,token)
    begin
      item = rpc_server.call("brax.users.heartItem",item_id.to_i,token)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "#{m.sourenick} hearts item #{item_id}"
    @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] hearted"
  end

  def do_info(item_id,m,token)
    begin
      item = rpc_server.call("brax.items.getItem",item_id.to_i)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end
    log "Info for item #{item_id} requested"
    if item['hidden']=="1"
      @bot.notice m.sourcenick, "Item [#{B}#{item_id}#{B}] is currently hidden. You must unhide it first before viewing."
    else
      out = "#{item['nsfw']=='1' ? '[NSFW] ' : ''}#{item['title']} [#{B}#{item_id}#{B}] "
      out = out + "(posted by #{item['author']}) " if item['queued']=="0" or !item['flags'].empty?
      out = out + shorten_url(item['permalink']+'?source=irc')
      out = out + " (Queued, #{item['approval_percentage']}% approval)" if item['queued']=="1"
      out = out + " (Flagged as #{item['flag']})" if !item['flag'].empty?
      out = out + " *Sponsored Item*" if !item['pp_item_number'].empty?
      m.reply out
    end
  end

  def identify(m, params)
    log "Attempting identification of '#{m.sourceaddress}' as user '#{params[:username].to_s}'"
    begin
      token = rpc_server.call("brax.auth.identify",params[:username].to_s,params[:password].to_s)
    rescue XMLRPC::FaultException => e
      @bot.notice m.sourcenick, e.faultString and return
    end

    sessions = @registry[:sessions]
    sessions << AuthSession.new(m.sourceaddress, token.to_s)
    @registry[:sessions] = sessions.uniq

    log "Identified '#{m.sourceaddress}' as user '#{params[:username].to_s}' (token: #{token})"
    @bot.mode(CHANNEL, "+v", m.sourcenick)
    m.okay
  end

  private

    def identified_token(address)
      @registry[:sessions].each { |session| return session.token if address == session.address }
      return nil
    end

    def shorten_url(url)
      res = Net::HTTP.post_form(URI.parse('http://borurl.com/api/'), {'action'=>'shorturl','format'=>'simple','url'=>url})
      res.body.empty? ? url : res.body
    end

    def format_ts(timestamp)
      Time.at(timestamp.to_i).strftime("%m/%d/%Y")
    end

    def submit_item(url,m,token)
      url = url.strip
      ok, item = rpc_server.call2("brax.items.getItemByUrl",url)
      if ok
        # item exists, update it
        begin
          # todo: the API should be changed to allow for
          # passing a token instead of nick
          rpc_server.call("brax.items.updateItem", item['item_id'].to_i, {:updated=>Time.now.to_i,:reposted_by=>m.sourcenick}, token)
        rescue XMLRPC::FaultException => e
          @bot.notice m.sourcenick, e.faultString and return
        end
        m.reply "#{item['letter']}: #{item['title']} (Updated [#{B}#{item['item_id']}#{B}])" + (item['hidden']==1 ? " (Hidden)" : "")
        m.reply "Item [#{item['item_id']}] already existed, posted #{format_ts(item['time'])} by #{B}#{item['author']}#{B} to #{item['channel_name']}"
        log "Updating existing item #{item['item_id']}"
      else
        # url doesn't exist, add it
        begin
          new_item = rpc_server.call("brax.items.addUrlItem", token, url, '')
        rescue XMLRPC::FaultException => e
          @bot.notice m.sourcenick, e.faultString and return
        end
        m.reply "#{new_item['letter']}: #{new_item['title']} [#{B}#{new_item['item_id']}#{B}]"
        log "Adding new item #{new_item['item_id']}"
      end
    end

    def do_comment(item_id,m,comment,token)
      begin
        comment = rpc_server.call("brax.items.addComment", item_id.to_i, token, comment)
      rescue XMLRPC::FaultException => e
        @bot.notice m.sourcenick, e.faultString and return
      end
      log "Adding comment #{comment['comment_id']} to item #{item_id}"
      @bot.notice m.sourcenick, "Comment #{B}#{comment['comment_id']}#{B} added."
    end

end
plugin = BraxPlugin.new
plugin.map 'brax enable', :action => "enable"
plugin.map 'brax disable', :action => "disable"
plugin.map 'brax status', :action => "status"
plugin.map 'brax ignore :action :option', :action => "ignore"
plugin.map 'brax ignore :action', :action => "ignore"
plugin.map 'brax ignore', :action => "ignore"
plugin.map 'queue', :action=>"queue"
plugin.map 'q', :action=>"queue"
plugin.map 'search *terms', :action=>"search"
plugin.map 's *terms', :action=>"search"
plugin.map 'quote [:nick]', :action=>"quote"
plugin.map 'addquote *quote', :action=>"addquote"
plugin.map 'delcomment :comment_id [:force]', :action=>"delcomment"
plugin.map 'online', :action=>"online"
plugin.map 'identify :username :password', :action => "identify", :public => false
